<?php

namespace Tictactoe\Ai;

use Tictactoe\Game as Game;
use Tictactoe\Tool as Tool;
use Tictactoe\Ai\Population\Item as Item;

class Move {

    /**
     * @var Game
     */
    protected $game = null;

    /**
     * @var int 
     */
    protected $iterationQty = 2;

    /**
     * @param Game $game
     */
    public function __construct(Game $game) {
        $this->setGame($game);
    }

    /**
     * @return Game
     */
    public function getGame() {
        return $this->game;
    }

    /**
     * @param Game $game
     */
    public function setGame(Game $game) {
        $this->game = $game;
    }

    /**
     * @return int
     */
    public function getIterationQty() {
        return $this->iterationQty;
    }

    /**
     * @param int $iterationQty
     */
    public function setIterationQty($iterationQty) {
        $this->iterationQty = $iterationQty;
    }

    /**
     * Wykonuje ruch w grze dla konkretnej wartosci 1 - kółko (O) lub 2 - krzyżyk (X)
     * 
     * @param int $v
     * @return Item
     */
    public function getNextMoveForValue($v) {

        try {
            $oponentValue = Tool::getOponentValue($v);

            $siblingValue = $v;
            if ($this->getGame()->isFirstMoveForValue($v)) {
                $siblingValue = $oponentValue;
            }

            /**
             * generowanie populacji z ktorej zostanie wybrany najlepszy mozliwy ruch
             */
            $population = new Population();
            $population->setPopulationQty(11 * 11);
            $population = $this->getPopulationForValue($population, $this->getGame(), $v, $siblingValue);
            $items = $population->getItems();

            /**
             * weryfikacja osobnikow tej populacji
             */
            $populationTemp = new Population();
            $populationTemp->setPopulationQty(10);
            $populationTemp->setOponentOwnerFlag(true);

            foreach ($population->getItems() as $key => $item) {
                /**
                 * 1. wygenerowanie ruchow przeciwnika na grze z osobnika
                 * 2. jezeli osobnik w ktoryms ruchu wygrywa to usuwamy osobnika z populacji pierwotnej
                 */
                try {

                    $populationTemp = $this->getPopulationForValue($populationTemp, $item->getGame(), $oponentValue, $oponentValue);
                    
                } catch (Exception\EstimateWinner $e) {
                    $best = $e->getPopulationItem();
                    $best->setForValue($v);
                    $best->setInfo('Exception\EstimateWinner from oponent population, you can lose :( ');
                    return $best;
                }
            }

            /**
             * sprawdzenie tablicy oponentAlmostWin z populacji ruchow przeciwnika
             */
            $oponentItems = $populationTemp->getOponentAlmostWinItems();
            if (count($oponentItems) > 0) {
                $best = $this->getBestItem($oponentItems);
                $best->setInfo('algorythm ended, oponent has almost win items in his population !');
                return $best;
            }

            $best = $this->getBestItem($items);
            $best->setInfo('algorythm ended');
            return $best;
                    
        } catch (Exception\EstimateWinner $e) {
            $best = $e->getPopulationItem();
            $best->setInfo('Exception\EstimateWinner from player population, you win :) ');
            return $best;
        }
    }

    /**
     * @param Game $game
     * @param int $v
     * @param int $qty
     * @param int $siblingValue
     * @return Population
     */
    protected function getPopulationForValue(Population $population, Game $game, $v, $siblingValue = 0) {

        $size = $game->getSize();
        for ($x = 0; $x < $size; $x++) {
            for ($y = 0; $y < $size; $y++) {

                $value = $game->getValue($x, $y);

                if ($value == 0 && ($siblingValue === 0 || $game->hasSiblings($x, $y, $siblingValue))) {
                    $gameClone = clone $game;
                    $gameClone->setValue($x, $y, $v);
                    $population->addItem($gameClone, $x, $y, $v);
                }
            }
        }

        return $population;
    }

    /**
     * @param Item[] $items
     * @return array
     * @throws Exception
     */
    public function getBestItem($items) {
        /**
         * wybranie najlepszych osobnikow
         */
        $res = array();
        $estimateMax = 0;
        foreach ($items as $one) {
            if ($one->getEstimate() >= $estimateMax) {
                $res[] = $one;
                $estimateMax = $one->getEstimate();
            } else {
                break;
            }
        }

        if (count($res) <= 0) {
            throw new Exception('There is not population item in population set !');
        }


        shuffle($res);

        return $res[0];
    }

}
